package run;

import influence.Influence;
import influence.InfluenceGenerator;

import java.util.LinkedList;
import java.util.List;

import action.Action;
import action.ActionGenerator;
import action.DirectionMutatingAction;

import paint.SandPainter;
import processing.core.PApplet;
import processing.core.PVector;
import event.Event;
import event.EventObserver;
import event.collide.CollideRule;
import event.collide.InflectionDirectionRule;
import event.collide.RandomPastPerpendicularSpawnRule;
import event.dist.DistRule;
import event.dist.RepulseRule;

public class LineCell {
	
	public int generation;
	public PVector loc;
	public PVector dir;
	public PApplet p;
	public boolean dead = false;
	public SandPainter paint;
	public List<PVector> pastLocs;
	List<DirectionMutatingAction> dirChangers = new LinkedList<DirectionMutatingAction>();
	List<DistRule> distRules = new LinkedList<DistRule>();
	
	int lifetime;
	
	private List<CollideRule> collideRules = new LinkedList<CollideRule>();

	int r,g,b;
	private EventObserver eventObserver;
	private InfluenceGenerator influenceAggregator;
	private ActionGenerator actionGenerator;
	
	public LineCell(PVector pVector, PVector dir, PApplet p, int gen) {
		loc = pVector;
		this.dir = dir;
		dir.normalize();
		this.generation = gen;
		this.p = p;
		
		paint = new SandPainter(p);
		pastLocs = new LinkedList<PVector>();
		
		// ActionQueue
		// REPLACE with rule handler, will choose for us
		collideRules.add(new RandomPastPerpendicularSpawnRule());
		collideRules.add(new InflectionDirectionRule());
		
		
		eventObserver = new EventObserver(this);
		influenceAggregator = new InfluenceGenerator();
		actionGenerator = new ActionGenerator();
		
		//REPLACE with rule handler; will choose for us.
		distRules.add(new RepulseRule());
		
		r = (int) p.random(100,250);
		g = (int) p.random(40,150);
		b = (int) p.random(20,200);
		
		lifetime = (int) p.random(200,1000);
	}
	
	public int getAge() {
		return pastLocs.size();
	}
	
	// normalizes a value to a new domain
	private float castToRange(float min, float max, float omin, float omax, float value) {
		return ( value / (omax - omin) ) * (max - min);
	}
	
	public void move() {
		Env e = Env.instance;
		if (dead) return;
	    recordLocation(e);
	    
	    if (getAge() > lifetime) {
	    	//TODO death_event.notify()
	    	// death observer fires
	    	dead = true;
	    }

	    // paint(params)
	    linearStrokeDensity(loc, pastLocs.get(0));

	    
	    List<Event> currEvents = eventObserver.poll();
	    
	    // each event will spur a 'desire'; the desires must be aggregated.
	    List<Influence> influences = influenceAggregator.sum(currEvents);
	    
	// what are influences for...??? aggregating event influences; Net Influence computing.
	    
//	    List<Action> nextActionToDo = actionGenerator.generate(influences);
//	    exec(nextActionToDo);
	    
	    
	    //TODO ***********
	    // 			executing the ActionQueue; performing rotations, aggregating
	    //			repulsions, feeding inputs to threshold functs.
	    // ***************
	    // Direction Changers? Evals new params... part of the deciders.
	    // SUM( dir Changers )
	    for (DirectionMutatingAction d : dirChangers) {
	    	d.changeDir(dir);
	    }
	    // Distance-related rules. 
	    PVector distSum = new PVector(0,0,0);
	    for (LineCell c : e.cells) {
		    for (DistRule d : distRules) {
		    	distSum.add(d.execRule(this, c, e, p));
		    }
	    }
	    distSum.normalize();
	    distSum.mult(0.01f);
	    
	    // PAST PARAMS UPDATING
	    //   update Dir, Loc
	    dir.add(distSum);
	    dir.normalize();
	    loc.add(dir);


	    // BorderCollision rule
	    if (loc.x < 0 || loc.y<0 || loc.x> Env.xdim || loc.y > Env.ydim) {
	    	dead=true;
	    }

	    // ************** EVENT GENERATION; gather info to fire events that feed 
	    // 			the Rules which feed the ActionQueue ***************
	  //TODO EVENT OBSERVERS NOTIFY() handlers (the event rules)
	    // this is a CellCollision observer
	    // COLLISION; notify rules of collision.
	    LineCell collided = e.isCollide(this);
	    if (collided != null) {
	    	dead=true;
	    	// TODO pick random past locs to spawn!(doing all rules???)
	    	for (CollideRule r : collideRules) {
	    		r.executeRule(this, collided, e, p);
	    	}
	    }
	  
	}
	
	private void hazyLine() {
		// draw the hazy line
	    /*
	    float hw = 3;
	    for (int i = 0; i < 5; i++) {
	      float hazyX = x+random(-hw, hw);
	      float hazyY = y+random(-hw, hw);
	      PVector hazy = new PVector(hazyX, hazyY, 0);
	      // dist can be 0 to .43*5=2.5... mult by 30
	      float strokeAlph = 10 + 30 / (loc.dist(hazy) * loc.dist(hazy));
	      p.stroke(r,g,b, strokeAlph);
	      p.point(hazyX, hazyY);
	    }
	    */
	    
	}

	// we want strokedensity a function of pos, pos-original, 
	//     controlling parameters of alpha and spread
	private void linearStrokeDensity(PVector curPos, PVector origPos) {
	    
	    float x = curPos.x;
	    float y = curPos.y;
	    
	    // TODO have some of these be random variables...
	    //TODO some start weak and end strong, others start strong and end weak...
	    // perhaps tie it to their proximity to other lines!!!
	    
	    float max_dens = 30;
	    float min_dens = 5;
	    float reduct_rate = 0.5F;
	    int point_count = 3;
	    float dens = larger(max_dens - reduct_rate * (curPos.dist(origPos)), min_dens);
	    
	    // let the width vary proportionately to the density. coherence.
	    float max_width = 3f;
	    float width = castToRange(1F, max_width, min_dens, max_dens, dens);
	    for (int i = 0; i < point_count; i++) {

	    	// ALSO depend upon generation(a bit) & dist from nearest neighbors...
	    	// alpha smaller the greater the width
	    	p.stroke(r,g,b, 80 / ( width + 0.15F*generation ));
		    p.point(x+random(-width,width),y+random(-width,width));
	    }
	}
	
	private float larger(float x, float y) {
		if (x > y)
			return x;
		else return y;
	}

	private void recordLocation(Env e) {
		pastLocs.add(new PVector(loc.x, loc.y, 0));
	    e.addHist(this, loc);
	}

	private float random(float x, float y) {
		return p.random(x,y);
	}

	public void addDirChanger(DirectionMutatingAction d) {
		dirChangers.add(d);
	}
	
	
}
